#include <sys/stat.h>
#include <unistd.h>

#include "PNGdec.h"
#include "zlib.h"

static uint32_t PNGParseInfo(PNGIMAGE *pPage)
{
	uint8_t *s = pPage->ucFileBuf;
	int iBytesRead;

	pPage->iHasAlpha = pPage->iInterlaced = 0;
	pPage->PNGFile.iPos = 0;
	//Read a few bytes to just parse the size/pixel info
	iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile, s, 32);
	if (iBytesRead < 32) {						//a PNG file this tiny? probably bad
		pPage->iError = PNG_INVALID_FILE;
		return pPage->iError;
	}

	char png_file_id[] = { 0x0a, 0x1a, 0x0a, 0x0d, 0x47, 0x4e, 0x50, 0x89 };
	if (memcpy(png_file_id, s, 8) == 0) {				//check that it's a PNG file
		pPage->iError = PNG_INVALID_FILE;
		return pPage->iError;
	}

	PNGBLCOK *block = (PNGBLCOK *)(s + 8);
	if (strncmp(block->type, "IHDR", 4) != 0) {
		pPage->iError = PNG_INVALID_FILE;
		return pPage->iError;
	}

	PNGIHDR *ihdr = (PNGIHDR *)block->data;

	pPage->iWidth = __builtin_bswap32(ihdr->width);			//MOTOLONG(&s[16]);
	pPage->iHeight = __builtin_bswap32(ihdr->height);		//MOTOLONG(&s[20]);
	pPage->ucBpp = ihdr->bit_depth;//s[24];				//bits per pixel
	pPage->ucPixelType = ihdr->color_type;//s[25];			//pixel type
	pPage->iInterlaced = ihdr->interlace_method;//s[28];
	if (pPage->iInterlaced || pPage->ucBpp > 8) {			//16-bit pixels are not supported (yet)
		pPage->iError = PNG_UNSUPPORTED_FEATURE;
		return pPage->iError;
	}

	// calculate the number of bytes per line of pixels
	switch (pPage->ucPixelType) {
		case PNG_PIXEL_GRAYSCALE:	//grayscale
		case PNG_PIXEL_INDEXED:		//indexed
			pPage->iPitch = (pPage->iWidth * pPage->ucBpp + 7) >> 3; // bytes per pixel
			break;
		case PNG_PIXEL_TRUECOLOR:	//truecolor
			pPage->iPitch = ((3 * pPage->ucBpp) * pPage->iWidth + 7) >> 3;
			break;
		case PNG_PIXEL_GRAY_ALPHA:	//grayscale + alpha
			pPage->iPitch = ((2 * pPage->ucBpp) * pPage->iWidth + 7) >> 3;
			pPage->iHasAlpha = 1;
			break;
		case PNG_PIXEL_TRUECOLOR_ALPHA:	//truecolor + alpha
			pPage->iPitch = ((4 * pPage->ucBpp) * pPage->iWidth + 7) >> 3;
			pPage->iHasAlpha = 1;
	}

	if (pPage->iPitch >= PNG_MAX_BUFFERED_PIXELS)
		return PNG_TOO_BIG;

	return PNG_SUCCESS;
}

int PNGInit(PNGIMAGE *pPNG)
{
	return PNGParseInfo(pPNG);	//gather info for image
}

static int32_t readMem(PNGFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
	int32_t iBytesRead;

	iBytesRead = iLen;
	if ((pFile->iSize - pFile->iPos) < iLen)
		iBytesRead = pFile->iSize - pFile->iPos;

	if (iBytesRead <= 0)
		return 0;

	memcpy(pBuf, &pFile->pData[pFile->iPos], iBytesRead);
	pFile->iPos += iBytesRead;

	return iBytesRead;
}

static int32_t seekMem(PNGFILE *pFile, int32_t iPosition)
{
	if (iPosition < 0)
		iPosition = 0;
	else if (iPosition >= pFile->iSize)
		iPosition = pFile->iSize - 1;
	pFile->iPos = iPosition;

	return iPosition;
}

int32_t PNG_openRAM(PNGIMAGE *pPNG, uint8_t *pData, int iDataSize, PNG_DRAW_CALLBACK *pfnDraw)
{
	pPNG->iError = PNG_SUCCESS;
	pPNG->pfnRead = readMem;
	pPNG->pfnSeek = seekMem;
	pPNG->pfnDraw = pfnDraw;
	pPNG->pfnOpen = NULL;
	pPNG->pfnClose = NULL;
	pPNG->PNGFile.iSize = iDataSize;
	pPNG->PNGFile.pData = pData;

	return PNGInit(pPNG);
}

static void *openFile(const char *szFilename, int32_t *pFileSize)
{
	struct stat st;
	FILE *fp;

	if (stat(szFilename, &st) != 0) {
		printf("PNGdec: Failed to open file %s\n", szFilename);
		return NULL;
	}

	*pFileSize = st.st_size;
	printf("PNGdec: file %s size: %lu\n", szFilename, st.st_size);

	fp = fopen(szFilename, "rb");
	if (fp == NULL) {
		printf("PNGdec: Failed to open file for reading\n");
		return NULL;
	}

	fseek(fp, 0, SEEK_SET);

	return fp;
}

static void closeFile(void *pHandle)
{
	if (NULL == pHandle)
		return;

	fclose(pHandle);
}

static int32_t readFile(PNGFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
	int32_t iBytesRead;

	iBytesRead = iLen;
	if ((pFile->iSize - pFile->iPos) < iLen)
		iBytesRead = pFile->iSize - pFile->iPos;

	if (iBytesRead <= 0)
		return 0;

	fseek(pFile->fHandle, pFile->iPos, SEEK_SET);
	fread(pBuf, 1, iBytesRead, pFile->fHandle);
	pFile->iPos += iBytesRead;

	return iBytesRead;
}

static int32_t seekFile(PNGFILE *pFile, int32_t iPosition)
{
	if (iPosition < 0)
		iPosition = 0;
	else if (iPosition >= pFile->iSize)
		iPosition = pFile->iSize - 1;
	pFile->iPos = iPosition;

	fseek(pFile->fHandle, iPosition, SEEK_SET);

        return iPosition;
}

int32_t PNG_openFile(PNGIMAGE *pPNG, const char *szFilename, PNG_DRAW_CALLBACK *pfnDraw)
{
	pPNG->iError = PNG_SUCCESS;
	pPNG->pfnRead = readFile;
	pPNG->pfnSeek = seekFile;
	pPNG->pfnDraw = pfnDraw;
	pPNG->pfnOpen = openFile;
	pPNG->pfnClose = closeFile;

	pPNG->PNGFile.fHandle = pPNG->pfnOpen(szFilename, &pPNG->PNGFile.iSize);
	if (pPNG->PNGFile.fHandle == NULL)
		return -1;

	return PNGInit(pPNG);
}

void PNG_close(PNGIMAGE *pPNG)
{
	if (!pPNG->pfnClose)
		return;

	pPNG->pfnClose(pPNG->PNGFile.fHandle);
}

int PNG_getWidth(PNGIMAGE *pPNG)
{
	return pPNG->iWidth;
}

int PNG_getHeight(PNGIMAGE *pPNG)
{
	return pPNG->iHeight;
}

int PNG_getLastError(PNGIMAGE *pPNG)
{
	return pPNG->iError;
}

uint8_t *PNG_getPalette(PNGIMAGE *pPNG)
{
	return pPNG->ucPalette;
}

int PNG_getBufferSize(PNGIMAGE *pPNG)
{
	return pPNG->iHeight * pPNG->iPitch;
}

uint8_t *PNG_getBuffer(PNGIMAGE *pPNG)
{
	return pPNG->pImage;
}

static uint8_t PNGMakeMask(PNGDRAW *pDraw, uint8_t *pMask, uint8_t ucThreshold)
{
	uint8_t alpha, c, *s, *d, *pPal;
	uint8_t cHasOpaque = 0;
	int i, x;

	switch (pDraw->iPixelType) {
		case PNG_PIXEL_TRUECOLOR_ALPHA: // truecolor + alpha
			s = pDraw->pPixels;
			d = pMask;
			for (x = 0; x < pDraw->iWidth; x += 8) { // groups of 8 pixels in each byte of mask
				c = 0;
				for (i = 0; i < 8; i++) {
					c <<= 1;
					alpha = s[3];
					if (alpha >= ucThreshold) // if opaque 'enough', set the bit
						c |= 1;
					s += 4;
				}
				*d++ = c;
				cHasOpaque |= c;
			}
			break;
		case PNG_PIXEL_GRAY_ALPHA:
			s = pDraw->pPixels;
			d = pMask;
			for (x = 0; x < pDraw->iWidth; x += 8) { // groups of 8 pixels in each byte of mask
				c = 0;
				for (i = 0; i < 8; i++) {
					c <<= 1;
					alpha = s[1];
					if (alpha >= ucThreshold) // if opaque 'enough', set the bit
						c |= 1;
					s += 2;
				}
				*d++ = c;
				cHasOpaque |= c;
			}
			break;
		case PNG_PIXEL_INDEXED:
			s = pDraw->pPixels;
			pPal = &pDraw->pPalette[768];
			d = pMask;
			for (x = 0; x < pDraw->iWidth; x += 8) { // groups of 8 pixels in each byte of mask
				uint8_t ucPix = 0;
				c = 0;
				switch (pDraw->iBpp) {
					case 2:
						for (i = 0; i < 8; i++) {
							if (i == 0 || i == 4)
								ucPix = *s++;
							c <<= 1;
							alpha = pPal[ucPix >> 6]; // get palette alpha for this color
							if (alpha >= ucThreshold) // if opaque 'enough', set the bit
								c |= 1;
							ucPix <<= 2;
						}
						break;
					case 4:
						for (i = 0; i < 8; i++) {
							if ((i & 1) == 0)
								ucPix = *s++;
							c <<= 1;
							alpha = pPal[ucPix >> 4]; // get palette alpha for this color
							if (alpha >= ucThreshold) // if opaque 'enough', set the bit
								c |= 1;
							ucPix <<= 4;
						}
						break;
					case 8:
						for (i = 0; i < 8; i++) {
							c <<= 1;
							alpha = pPal[s[0]]; // get palette alpha for this color
							if (alpha >= ucThreshold) // if opaque 'enough', set the bit
								c |= 1;
							s++;
						}
						break;
				} // switch on bit depth
				*d++ = c;
				cHasOpaque |= c;
			}
			break;
		default: // No alpha channel; make a mask of all 1's
			memset(pMask, 0xff, (pDraw->iWidth + 7) >> 3);
			cHasOpaque = 1;
			break;
	} // switch on pixel type

	return cHasOpaque; // let the caller know if any pixels are opaque
}
#if 0
//
// Convert a line of native PNG pixels into RGB565
// handles all standard pixel types
// written for simplicity, not necessarily performance
//
static void PNGRGB565(PNGDRAW *pDraw, uint16_t *pPixels, int iEndiannes, uint32_t u32Bkgd, int iHasAlpha)
{
	int x, j;
	uint16_t usPixel, *pDest = pPixels;
	uint8_t c, a, *pPal, *s = pDraw->pPixels;

	switch (pDraw->iPixelType) {
		case PNG_PIXEL_GRAY_ALPHA:
			for (x = 0; x < pDraw->iWidth; x++) {
				c = *s++; // gray level
				a = *s++;
				j = (a * c) >> 8; // multiply by the alpha
				usPixel = usGrayTo565[j];
				if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
					usPixel = __builtin_bswap16(usPixel);
				*pDest++ = usPixel;
			}
			break;
		case PNG_PIXEL_GRAYSCALE:
			for (x = 0; x < pDraw->iWidth; x++) {
				c = *s++;
				usPixel = (c >> 3); // blue
				usPixel |= ((c >> 2) << 5); // green
				usPixel |= ((c >> 3) << 11); // red
				if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
					usPixel = __builtin_bswap16(usPixel);
				*pDest++ = usPixel;
			}
			break;
		case PNG_PIXEL_TRUECOLOR:
			for (x = 0; x < pDraw->iWidth; x++) {
				usPixel = (s[2] >> 3); // blue
				usPixel |= ((s[1] >> 2) << 5); // green
				usPixel |= ((s[0] >> 3) << 11); // red
				if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
					usPixel = __builtin_bswap16(usPixel);
				*pDest++ = usPixel;
				s += 3;
			}
			break;
		case PNG_PIXEL_INDEXED: // palette color (can be 1/2/4 or 8 bits per pixel)
			if (pDraw->pFastPalette && !pDraw->iHasAlpha) { // faster RGB565 palette exists
				switch (pDraw->iBpp) {
					case 8:
						for (x = 0; x < pDraw->iWidth; x++) {
							c = *s++;
							usPixel = pDraw->pFastPalette[c];
							if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
								usPixel = __builtin_bswap16(usPixel);
							*pDest++ = usPixel;
						}
						break;
					case 4:
						for (x = 0; x < pDraw->iWidth; x += 2) {
							c = *s++;
							usPixel = pDraw->pFastPalette[c >> 4];
							if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
								usPixel = __builtin_bswap16(usPixel);
							*pDest++ = usPixel;
							usPixel = pDraw->pFastPalette[c & 0xf];
							if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
								usPixel = __builtin_bswap16(usPixel);
							*pDest++ = usPixel;
						}
						break;
					case 2:
						for (x = 0; x < pDraw->iWidth; x += 4) {
							c = *s++;
							for (j = 0; j < 4; j++) { // work on pairs of bits
								usPixel = pDraw->pFastPalette[c >> 6];
								if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
									usPixel = __builtin_bswap16(usPixel);
								*pDest++ = usPixel;
								c <<= 2;
							}
						}
						break;
					case 1:
						for (x = 0; x < pDraw->iWidth; x += 4) {
							c = *s++;
							for (j = 0; j < 8; j++) { // work on pairs of bits
								usPixel = pDraw->pFastPalette[c >> 7];
								if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
									usPixel = __builtin_bswap16(usPixel);
								*pDest++ = usPixel;
								c <<= 1;
							}
						}
						break;
				} // switch on bpp
				return;
			}

			switch (pDraw->iBpp) {
				case 8: // 8-bit palette also supports palette alpha
					if (pDraw->iHasAlpha) { // use the alpha to modify the palette
						for (x = 0; x < pDraw->iWidth; x++) {
							int a;
							c = *s++;
							a = pDraw->pPalette[768 + c]; // get alpha
							pPal = &pDraw->pPalette[c * 3];
							usPixel = ((pPal[2] * a) >> 11); // blue
							usPixel |= (((pPal[1] * a) >> 10) << 5); // green
							usPixel |= (((pPal[0] * a) >> 11) << 11); // red
							if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
								usPixel = __builtin_bswap16(usPixel);
							*pDest++ = usPixel;
						} // for x
					} else {
						for (x = 0; x < pDraw->iWidth; x++) {
							c = *s++;
							pPal = &pDraw->pPalette[c * 3];
							usPixel = (pPal[2] >> 3); // blue
							usPixel |= ((pPal[1] >> 2) << 5); // green
							usPixel |= ((pPal[0] >> 3) << 11); // red
							if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
								usPixel = __builtin_bswap16(usPixel);
							*pDest++ = usPixel;
						} // for x
					} // not alpha palette
					break;
				case 4:
					for (x = 0; x < pDraw->iWidth; x += 2) {
						c = *s++;
						pPal = &pDraw->pPalette[(c >> 4) * 3];
						usPixel = (pPal[2] >> 3); // blue
						usPixel |= ((pPal[1] >> 2) << 5); // green
						usPixel |= ((pPal[0] >> 3) << 11); // red
						if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
							usPixel = __builtin_bswap16(usPixel);
						*pDest++ = usPixel;
						pPal = &pDraw->pPalette[(c & 0xf) * 3];
						usPixel = (pPal[2] >> 3); // blue
						usPixel |= ((pPal[1] >> 2) << 5); // green
						usPixel |= ((pPal[0] >> 3) << 11); // red
						if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
							usPixel = __builtin_bswap16(usPixel);
						*pDest++ = usPixel;
					}
					break;
				case 2:
					for (x = 0; x < pDraw->iWidth; x += 4) {
						c = *s++;
						for (j = 0; j < 4; j++) { // work on pairs of bits
							pPal = &pDraw->pPalette[(c >> 6) * 3];
							usPixel = (pPal[2] >> 3); // blue
							usPixel |= ((pPal[1] >> 2) << 5); // green
							usPixel |= ((pPal[0] >> 3) << 11); // red
							if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
								usPixel = __builtin_bswap16(usPixel);
							*pDest++ = usPixel;
							c <<= 2;
						}
					}
					break;
				case 1:
					for (x = 0; x < pDraw->iWidth; x += 4) {
						c = *s++;
						for (j = 0; j < 8; j++) { // work on pairs of bits
							pPal = &pDraw->pPalette[(c >> 7) * 3];
							usPixel = (pPal[2] >> 3); // blue
							usPixel |= ((pPal[1] >> 2) << 5); // green
							usPixel |= ((pPal[0] >> 3) << 11); // red
							if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
								usPixel = __builtin_bswap16(usPixel);
							*pDest++ = usPixel;
							c <<= 1;
						}
					}
					break;
			} // switch on bits per pixel
			break;
		case PNG_PIXEL_TRUECOLOR_ALPHA: // truecolor + alpha
			if (u32Bkgd != 0xffffffff) { // user wants to blend it with a background color
				uint32_t r, g, b, a;
				uint32_t b_r, b_g, b_b;
				b_r = u32Bkgd & 0xff; b_g = (u32Bkgd & 0xff00) >> 8;
				b_b = (u32Bkgd >> 16) & 0xff;
				uint16_t u16Clr = (u32Bkgd & 0xf8) << 8;
				u16Clr |= ((u32Bkgd & 0xfc00) >> 5);
				u16Clr |= ((u32Bkgd & 0xf80000) >> 19);
				for (x = 0; x < pDraw->iWidth; x++) {
					r = s[0]; g = s[1]; b = s[2]; a = s[3];
					if (a == 0)
						usPixel = u16Clr;
					else if (a == 255) { // fully opaque
						usPixel = (s[2] >> 3); // blue
						usPixel |= ((s[1] >> 2) << 5); // green
						usPixel |= ((s[0] >> 3) << 11); // red
					} else { // mix the colors
						r = ((r * a) + (b_r * (255-a))) >> 8;
						g = ((g * a) + (b_g * (255-a))) >> 8;
						b = ((b * a) + (b_b * (255-a))) >> 8;
						usPixel = (b >> 3); // blue
						usPixel |= ((g >> 2) << 5); // green
						usPixel |= ((r >> 3) << 11); // red
					}
					if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
						usPixel = __builtin_bswap16(usPixel);
					*pDest++ = usPixel;
					s += 4; // skip alpha
				}
			} else { // ignore alpha
				for (x = 0; x < pDraw->iWidth; x++) {
					usPixel = (s[2] >> 3); // blue
					usPixel |= ((s[1] >> 2) << 5); // green
					usPixel |= ((s[0] >> 3) << 11); // red
					if (iEndiannes == PNG_RGB565_BIG_ENDIAN)
						usPixel = __builtin_bswap16(usPixel);
					*pDest++ = usPixel;
					s += 4; // skip alpha
				}
			}
			break;
	}
}
#endif
static int32_t readFLASH(PNGFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
	int32_t iBytesRead;

	iBytesRead = iLen;
	if ((pFile->iSize - pFile->iPos) < iLen)
		iBytesRead = pFile->iSize - pFile->iPos;

	if (iBytesRead <= 0)
		return 0;

	memcpy(pBuf, &pFile->pData[pFile->iPos], iBytesRead);
	pFile->iPos += iBytesRead;

	return iBytesRead;
}

static void DeFilter(uint8_t *pCurr, uint8_t *pPrev, int iWidth, int iPitch)
{
	uint8_t ucFilter = *pCurr++;
	int x, iBpp;

	if (iPitch <= iWidth)
		iBpp = 1;
	else
		iBpp = iPitch / iWidth;

	pPrev++; // skip filter of previous line
	switch (ucFilter) { // switch on filter type
		case PNG_FILTER_NONE:
			// nothing to do :)
			break;
		case PNG_FILTER_SUB:
			for (x = iBpp; x < iPitch; x++) {
				pCurr[x] += pCurr[x-iBpp];
			}
			break;
		case PNG_FILTER_UP:
			for (x = 0; x < iPitch; x++) {
				pCurr[x] += pPrev[x];
			}
			break;
		case PNG_FILTER_AVG:
			for (x = 0; x < iBpp; x++) {
				pCurr[x] = (pCurr[x] + pPrev[x] / 2 );
			}
			for (x = iBpp; x < iPitch; x++) {
				pCurr[x] = pCurr[x] + (pPrev[x] + pCurr[x-iBpp]) / 2;
			}
			break;
		case PNG_FILTER_PAETH:
			if (iBpp == 1) {
				int a, c;
				uint8_t *pEnd = &pCurr[iPitch];
				// First pixel/byte
				c = *pPrev++;
				a = *pCurr + c;
				*pCurr++ = (uint8_t)a;
				while (pCurr < pEnd) {
					int b, pa, pb, pc, p;
					a &= 0xff; // From previous iteration
					b = *pPrev++;
					p = b - c;
					pc = a - c;
					// assume no native ABS() instruction
					pa = p < 0 ? -p : p;
					pb = pc < 0 ? -pc : pc;
					pc = (p + pc) < 0 ? -(p + pc) : p + pc;
					// choose the best predictor
					if (pb < pa) {
						pa = pb; a = b;
					}
					if (pc < pa) a = c;
					// Calculate current pixel
					c = b;
					a += *pCurr;
					*pCurr++ = (uint8_t)a;
				}
			} else { // multi-byte
				uint8_t *pEnd = &pCurr[iBpp];
				// first pixel is treated the same as 'up'
				while (pCurr < pEnd) {
					int a = *pCurr + *pPrev++;
					*pCurr++ = (uint8_t)a;
				}
				pEnd = pEnd + (iPitch - iBpp);
				while (pCurr < pEnd) {
					int a, b, c, pa, pb, pc, p;
					c = pPrev[-iBpp];
					a = pCurr[-iBpp];
					b = *pPrev++;
					p = b - c;
					pc = a - c;
					// asume no native ABS() instruction
					pa = p < 0 ? -p : p;
					pb = pc < 0 ? -pc : pc;
					pc = (p + pc) < 0 ? -(p + pc) : p + pc;
					if (pb < pa) {
						pa = pb; a = b;
					}
					if (pc < pa) a = c;
					a += *pCurr;
					*pCurr++ = (uint8_t)a;
				}
			} // multi-byte
			break;
	} // switch on filter type
} /* DeFilter() */

/* Decode the PNG file
 *
 * You must call open() before calling decode()
 * This function can be called repeatedly without having
 * to close and re-open the file */
int DecodePNG(PNGIMAGE *pPage, void *pUser, int iOptions)
{
	int err, y, iLen = 0;
	int bDone, iOffset, iFileOffset, iBytesRead;
	int iMarker = 0;
	uint8_t *tmp, *pCurr, *pPrev;
	z_stream d_stream; /* decompression stream */
	uint8_t *s = pPage->ucFileBuf;
	struct inflate_state *state;

	//Either the image buffer must be allocated or a draw callback must be set before entering
	if (pPage->pImage == NULL && pPage->pfnDraw == NULL) {
		pPage->iError = PNG_NO_BUFFER;
		printf(" Func: %s input para is null, %d \n", __func__, __LINE__);
		return 0;
	}
	//Use internal buffer to maintain the current and previous lines
	pCurr = pPage->ucPixels;
	pPrev = &pPage->ucPixels[pPage->iPitch+1];
	pPage->iError = PNG_SUCCESS;
	//Start decoding the image
	bDone = FALSE;
	//Inflate the compressed image data
	//The allocation functions are disabled and zlib has been modified
	//to not use malloc/free and instead the buffer is part of the PNG class
	d_stream.zalloc = (alloc_func)0;
	d_stream.zfree = (free_func)0;
	d_stream.opaque = (voidpf)0;
	// Insert the memory pointer here to avoid having to use malloc() inside zlib
	state = (struct inflate_state FAR *)pPage->ucZLIB;
	d_stream.state = (struct internal_state FAR *)state;
	state->window = &pPage->ucZLIB[sizeof(struct inflate_state)]; // point to 32k dictionary buffer
	err = inflateInit(&d_stream);
#ifdef FUTURE
	//if (inpage->cCompression == PIL_COMP_IPHONE_FLATE)
	//	err = mz_inflateInit2(&d_stream, -15); // undocumented option which ignores header and crcs
	//else
	//	err = mz_inflateInit2(&d_stream, 15);
#endif
	iFileOffset = 8;	//skip PNG file signature
	iOffset = 0;		//internal buffer offset starts at 0
	//Read some data to start
	(*pPage->pfnSeek)(&pPage->PNGFile, iFileOffset);
	iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile, s, PNG_FILE_BUF_SIZE);
	iFileOffset += iBytesRead;
	y = 0;
	d_stream.avail_out = 0;
	d_stream.next_out = pPage->pImage;

	printf(" Width: %03d   Height: %03d  Size:%d bytes \n", pPage->iWidth, pPage->iHeight, iBytesRead);

	while (y < pPage->iHeight) {			//continue until fully decoded
		//parse the markers until the next data block
		if (bDone)
			continue;
//		while (!bDone) {
		iLen = MOTOLONG(&s[iOffset]);	//chunk length
		if (iLen < 0 || iLen + (iFileOffset - iBytesRead) > pPage->PNGFile.iSize) {// invalid data
			pPage->iError = PNG_DECODE_ERROR;
			return 1;
		}
		iMarker = MOTOLONG(&s[iOffset + 4]);
		iOffset += 8; //point to the marker data
		switch (iMarker) {
			case 0x44474b62:	//'bKGD' DEBUG
				break;
			case 0x67414d41:	//'gAMA'
				break;
#ifdef FUTURE
			case 0x6663544C:	//'fcTL' frame control block for animated PNG (need to get size of this partial image)
				pPage->iWidth = MOTOLONG(&pPage->pData[iOffset + 4]);//frame width
				pPage->iHeight = MOTOLONG(&pPage->pData[iOffset + 8]);//frame height
				bDone = TRUE;
				break;
#endif
			case 0x504c5445: //'PLTE' palette colors
				//assume all colors are opaque unless specified
				memset(&pPage->ucPalette[768], 0xff, 256);
				memcpy(pPage->ucPalette, &s[iOffset], iLen);
				if (iOptions & PNG_FAST_PALETTE) {	//create a RGB565 palette
					int i, iColors = 1 << pPage->ucBpp;
					uint16_t usPixel, *d;
					uint8_t *s = pPage->ucPalette;
					d = (uint16_t *)&pPage->ucPixels[sizeof(pPage->ucPixels)-512];
					for (i = 0; i < iColors; i++) {
						usPixel = (s[2] >> 3);		//blue
						usPixel |= ((s[1] >> 2) << 5);	//green
						usPixel |= ((s[0] >> 3) << 11);	//red
						*d++ = usPixel;
						s += 3;
					}
				}
				break;
			case 0x74524e53: //'tRNS' transparency info
				if (pPage->ucPixelType == PNG_PIXEL_INDEXED) {//if palette exists
					memcpy(&pPage->ucPalette[768], &s[iOffset], iLen);
					pPage->iHasAlpha = 1;
				} else if (iLen == 2){//for grayscale images
					// lower part of 2-byte value is transparent color index
					pPage->iTransparent = s[iOffset + 1];
					pPage->iHasAlpha = 1;
				} else if (iLen == 6) {//transparent color for 24-bpp image
					//lower part of 2-byte value is transparent color value
					pPage->iTransparent = s[iOffset + 5];
					pPage->iTransparent |= (s[iOffset + 3] << 8);
					pPage->iTransparent |= (s[iOffset + 1] << 16);
					pPage->iHasAlpha = 1;
				}
				break;
			case 0x49444154: //'IDAT' image data block
				while (iLen) {
					if (iOffset >= iBytesRead) {
						// we ran out of data; get some more
						iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile,
								pPage->ucFileBuf, (iLen > PNG_FILE_BUF_SIZE) ? PNG_FILE_BUF_SIZE : iLen);
						iFileOffset += iBytesRead;
						iOffset = 0;
					} else {
						// number of bytes remaining in buffer
						iBytesRead -= iOffset;
					}
					d_stream.next_in = &pPage->ucFileBuf[iOffset];
					d_stream.avail_in = iBytesRead;
					iLen -= iBytesRead;
					if (iLen < 0)
						iLen = 0;
					iOffset += iBytesRead;
					//if (iMarker == 0x66644154) {
						//data starts at offset 4 in APNG frame data block
					//	d_stream.next_in += 4;
					//	d_stream.avail_in -= 4;
					//}
					err = 0;
					while (err == Z_OK) {
						if (d_stream.avail_out == 0) {
							// reset for next line
							d_stream.avail_out = pPage->iPitch + 1;
							d_stream.next_out = pCurr;
						}//otherwise it could be a continuation of an unfinished line
						err = inflate(&d_stream, Z_NO_FLUSH, iOptions & PNG_CHECK_CRC);
						if ((err == Z_OK || err == Z_STREAM_END) && d_stream.avail_out == 0) {
							// successfully decoded line
							DeFilter(pCurr, pPrev, pPage->iWidth, pPage->iPitch);
							if (pPage->pImage == NULL) { // no image buffer, send it line by line
								PNGDRAW pngd;
								pngd.pUser = pUser;
								pngd.iPitch = pPage->iPitch;
								pngd.iWidth = pPage->iWidth;
								pngd.pPalette = pPage->ucPalette;
								pngd.pFastPalette = (iOptions & PNG_FAST_PALETTE) ? (uint16_t *)&pPage->ucPixels[sizeof(pPage->ucPixels) - 512] : NULL;
								pngd.pPixels = pCurr + 1;
								pngd.iPixelType = pPage->ucPixelType;
								pngd.iHasAlpha = pPage->iHasAlpha;
								pngd.iBpp = pPage->ucBpp;
								pngd.y = y;
								(*pPage->pfnDraw)(&pngd);
							} else {
								//copy to destination bitmap
								memcpy(&pPage->pImage[y * pPage->iPitch], &pCurr[1], pPage->iPitch);
							}
							y++;
							//swap current and previous lines
							tmp = pCurr; pCurr = pPrev; pPrev = tmp;
						} else { // some error
							tmp = NULL;
						}
					}
					if (err == Z_STREAM_END && d_stream.avail_out == 0) {
						// successful decode, stop here
						y = pPage->iHeight;
						bDone = TRUE;
					} else  if (err == Z_DATA_ERROR || err == Z_STREAM_ERROR) {
						iLen = 0; // quit now
						y = pPage->iHeight;
						pPage->iError = PNG_DECODE_ERROR;
						bDone = TRUE; // force loop to exit with error
					} else if (err == Z_BUF_ERROR) {
						y |= 0; // need more data
					}
				} // while (iLen)
				if (y != pPage->iHeight && iFileOffset < pPage->PNGFile.iSize) {
					// need to read more IDAT chunks
					iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile, pPage->ucFileBuf, PNG_FILE_BUF_SIZE);
					iFileOffset += iBytesRead;
					iOffset = 0;
				}
				break;
				//case 0x69545874: //'iTXt'
				//case 0x7a545874: //'zTXt'
#ifdef FUTURE
			case 0x74455874://'tEXt'
				{
					char szTemp[256];
					char *pDest = NULL;
					memcpy(szTemp, &s[iOffset], 80); // get the label length (Title, Author, Description, Copyright, Creation Time, Software, Disclaimer, Warning, Source, Comment)
					i = (int)strlen(szTemp) + 1; // start of actual text
					if (strcmp(szTemp, "Comment") == 0 || strcmp(szTemp, "Description") == 0)
						pDest = &pPage->szComment[0];
					else if (strcmp(szTemp, "Software") == 0)
						pDest = &pPage->szSoftware[0];
					else if (strcmp(szTemp, "Author") == 0)
						pDest = &pPage->szArtist[0];
					if (pDest != NULL) {
						if ((iLen - i) < 128) {
							memcpy(pPage->szComment, &pPage->pData[iOffset + i], iLen - i);
							pPage->szComment[iLen - i + 1] = 0;
						} else {
							memcpy(pPage->szComment, &pPage->pData[iOffset + i], 127);
							pPage->szComment[127] = '\0';
						}
					}
				}
				break;
#endif
		}//switch
		iOffset += (iLen + 4); //skip data + CRC
		if (iOffset > iBytesRead - 8) { // need to read more data
			iFileOffset += (iOffset - iBytesRead);
			(*pPage->pfnSeek)(&pPage->PNGFile, iFileOffset);
			iBytesRead = (*pPage->pfnRead)(&pPage->PNGFile, s, PNG_FILE_BUF_SIZE);
			iFileOffset += iBytesRead;
			iOffset = 0;
		}
//		}//while !bDone
	}//while y < height
	err = inflateEnd(&d_stream);

	return pPage->iError;
}
